using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace net.bistre.pfwl {
	/// <summary>
	/// Port Forwarder With Logging
	/// </summary>
	class pfwl {

		/// <summary>
		/// Main entry point
		/// </summary>
		/// <param name="args">
		/// -inip <ip>       Incomming IP
		/// -inport <port>   Incomming Port
		/// -outip <ip>      Outgoing IP
		/// -outport <port>  Outgoing Port
		/// -log <file>      Log File
		/// </param>
		public static void Main(string[] args) {

			PfwlCore tsh = null;
			Thread t = null;
			IPAddress ipaIn = null;
			IPAddress ipaOut = null;
			TextWriter strmLog = null;

			Settings settings = new Settings();

			// Load command-line settings (if aviliable)
			settings = Settings.LoadFromArgs(args);

			// Intro Text
			Console.WriteLine("Port Forwarder With Logging");
			Console.WriteLine(String.Empty);
		
			// Syntax
			if ((settings.InIP == String.Empty) || (settings.OutIP == String.Empty) || (settings.InPort == 0) || (settings.OutPort == 0)) {
				Console.WriteLine("SYNTAX:");
				Console.WriteLine("  pfwl.exe -inip <ip> -inport <port> -outip <ip> -outport <port> -log <file>");
				Console.WriteLine("    -inip <ip>       Incomming IP. Default: 127.0.0.1");
				Console.WriteLine("    -inport <port>   Incomming Port");
				Console.WriteLine("    -outip <ip>      Outgoing IP. Default: 127.0.0.1");		
				Console.WriteLine("    -outport <port>  Outgoing Port");		
				Console.WriteLine("    -log <file>      Log to file rather then outputing to screen.");		
				Environment.Exit(0);
			}
		
			// Display Settings
			Console.WriteLine(String.Format("      In: {0} : {1}", settings.InIP, settings.InPort));
			Console.WriteLine(String.Format("     Out: {0} : {1}", settings.OutIP, settings.OutPort));
			Console.WriteLine(String.Format("     Out: {0} : {1}", settings.OutIP, settings.OutPort));
			Console.WriteLine(String.Format("Log File: {0}", settings.LogFile));
			Console.WriteLine(String.Empty);

			// If a log file was specified open it now
			if (settings.LogFile != String.Empty) {
				try {
					strmLog = new StreamWriter(File.Open(settings.LogFile, FileMode.Create, FileAccess.Write, FileShare.Read));
				} catch(Exception e) {
					Console.WriteLine("Unable to open/create Log file.\nException: {0}", e);
					Environment.Exit(0);
				}
			} else {
				strmLog = Console.Out;
			}

			// Convert IPs to objects
			try {
				ipaIn = ResolveIP(settings.InIP);

			} catch (Exception e) {
				Console.WriteLine("\nUnable to convert In IP.\n\nException: {0}", e);
				Environment.Exit(0);
			}

			try {
				ipaOut = ResolveIP(settings.OutIP);

			} catch(Exception e) {
				Console.WriteLine("\nUnable to convert Out IP.\n\nException: {0}", e);
				Environment.Exit(0);
			}

			// Create TCP socket
			Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		
			// Create local end point
			IPEndPoint ipep = new IPEndPoint(ipaIn, settings.InPort);
		
			// Bind to local endpoint
			try {
				server.Bind(ipep);
			} catch(SocketException e) {
				Console.WriteLine("Unable to bind socket to local.\nException: {0}", e); Environment.Exit(0);
			}

			//Start Listener
			server.Listen(10);
		
			Socket client;

			// Enter the listening loop.
			while(true) {
				// Perform a blocking call to accept requests.
				// You could also use server.AcceptSocket() here.
				Console.WriteLine("Waiting for connection....");
				client = server.Accept();
				Console.WriteLine("Incomming connection conected...");

				//Spawn a new thread to handle the Stream
				tsh = new PfwlCore(client, ipaOut, settings.OutPort, strmLog);
				t = new Thread(new ThreadStart(tsh.ThreadProc));
				t.Start();
			}
		}

		private static IPAddress ResolveIP(string ip) {
			try {
				// Try and resove as IP
				return IPAddress.Parse(ip);

			} catch (FormatException e) {
				
				// If invalid format, try resolving as Name
				try {
					return Dns.GetHostByName(ip).AddressList[0];

				} catch (Exception e2) {
					throw e2;
				}

			} catch (Exception e) {
				throw e;
			}
		}
	}
}